#ifndef PHYSICS_SERVER_H
#define PHYSICS_SERVER_H
//------------------------------------------------------------------------------
/**
    @class Physics::PhysicsServer

    The physics subsystem server object.

    (C) 2003 RadonLabs GmbH
*/
#include "core/refcounted.h"
#include "core/ptr.h"
#include "core/singleton.h"
#include "physics/materialtable.h"
#include "physics/contactpoint.h"
#include "physics/physicsentity.h"
#include "physics/ray.h"
#include "util/dictionary.h"
#include "timing/time.h"
#include "math/float2.h"
#include "math/line.h"
#define BAN_OPCODE_AUTOLINK
#include "opcode/opcode.h"

//------------------------------------------------------------------------------
namespace Physics
{
class Level;
class Composite;
class Ragdoll;
class RigidBody;
class Shape;
class BoxShape;
class SphereShape;
class MeshShape;
class CapsuleShape;
class Ray;
class HingeJoint;
class UniversalJoint;
class SliderJoint;
class BallJoint;
class Hinge2Joint;
class AMotor;
class MouseGripper;
class AreaImpulse;
class MeshCache;

class PhysicsServer : public Core::RefCounted
{
	__DeclareClass(PhysicsServer);
    __DeclareSingleton(PhysicsServer);

public:
    /// constructor
    PhysicsServer();
    /// destructor
    virtual ~PhysicsServer();

    /// initialize the physics subsystem
    virtual bool Open();
    /// close the physics subsystem
    virtual void Close();
    /// Is server open?
    bool IsOpen() const;
    /// perform simulation step(s)
    virtual void Trigger();
    /// set the current physics level object
    void SetLevel(Level* level);
    /// get the current physics level object
    Level* GetLevel() const;
    /// access to global embedded mouse gripper
    MouseGripper* GetMouseGripper() const;
    /// set the current simulation time
    void SetTime(Timing::Time t);
    /// get the current simulation time
    Timing::Time GetTime() const;
    /// set current point of interest
    void SetPointOfInterest(const Math::vector& p);
    /// get current point of interest
    const Math::vector& GetPointOfInterest() const;
    /// find a registered physics entity by its unique id
    PhysicsEntity* GetEntityByUniqueId(PhysicsEntity::Id uniqueId) const;
    /// create a new composite object
    virtual Composite* CreateComposite() const;
    /// create a new ragdoll object
    virtual Ragdoll* CreateRagdoll() const;
    /// create a new rigid body object
    virtual RigidBody* CreateRigidBody() const;
    /// create a new hinge joint object
    virtual HingeJoint* CreateHingeJoint() const;
    /// create a new hinge2 joint object
    virtual Hinge2Joint* CreateHinge2Joint() const;
    /// create a new universal joint object
    virtual UniversalJoint* CreateUniversalJoint() const;
    /// create new slider joint object
    virtual SliderJoint* CreateSliderJoint() const;
    /// create new ball joint object
    virtual BallJoint* CreateBallJoint() const;
    /// create a new amotor object
    virtual AMotor* CreateAMotor() const;
    /// create a box shape object
    virtual BoxShape* CreateBoxShape(const Math::matrix44& m, MaterialType matType, const Math::vector& size) const;
    /// create a sphere shape object
    virtual SphereShape* CreateSphereShape(const Math::matrix44& m, MaterialType matType, float radius) const;
    /// create a capsule shape object
    virtual CapsuleShape* CreateCapsuleShape(const Math::matrix44& m, MaterialType matType, float radius, float length) const;
    /// create a mesh shape object
    virtual MeshShape* CreateMeshShape(const Math::matrix44& m, MaterialType matType, const Util::String& meshFilename, int meshGroupIndex) const;
    /// create a ray object
    virtual Ray* CreateRay(const Math::vector& orig, const Math::vector& vec) const;
    /// Do a ray check starting from position `pos' along ray `dir'.
    bool RayCheck(const Math::vector& pos, const Math::vector& dir, const FilterSet& excludeSet);
    /// return contact points generated by RayCheck()
    const Util::Array<ContactPoint>& GetContactPoints() const;
    /// do a ray check through the mouse pointer and return closest contact
    const ContactPoint* GetClosestContactUnderMouse(const Math::line& worldMouseRay, const FilterSet& excludeSet);
    /// get closest contact along ray
    const ContactPoint* GetClosestContactAlongRay(const Math::vector& pos, const Math::vector& dir, const FilterSet& excludeSet);
    /// apply an impulse along a ray into the world onto the first object which the ray hits
    bool ApplyImpulseAlongRay(const Math::vector& pos, const Math::vector& dir, const FilterSet& excludeSet, float impulse);
    /// apply an area impule to the world
    void ApplyAreaImpulse(AreaImpulse* impulse);
    /// return all entities within a spherical area
    int GetEntitiesInSphere(const Math::vector& pos, float radius, const FilterSet& excludeSet, Util::Array<Ptr<PhysicsEntity> >& result);
    /// return all entities within a box 
    int GetEntitiesInBox(const Math::vector& scale, const Math::matrix44& m, const FilterSet& excludeSet, Util::Array<Ptr<PhysicsEntity> >& result);
    /// render a debug visualization of the level
    virtual void RenderDebug();
    /// convert Math::matrix44 to Ode matrix
    static void Matrix44ToOde(const Math::matrix44& from, dMatrix3& to);
    /// convert Ode matrix to Math::matrix44
    static void OdeToMatrix44(const dMatrix3& from, Math::matrix44& to);
    /// convert Math::vector to Ode vector
    static void Vector3ToOde(const Math::vector& from, dVector3& to);
    /// convert Ode vector to Math::vector
    static void OdeToVector3(const dVector3& from, Math::vector& to);
    /// get a unique stamp value
    static uint GetUniqueStamp();
    /// get ode world id
    dWorldID GetOdeWorldId() const;
    /// get ode dynamic collide space id
    dSpaceID GetOdeDynamicSpaceId() const;
    /// get ode static collide space id
    dSpaceID GetOdeStaticSpaceId() const;
    /// get the common collide space id which contains the static and dynamic collide space
    dSpaceID GetOdeCommonSpaceId() const;

    /// get scratch space by id
    dSpaceID GetScratchSpaceSpaceID(unsigned int id) const;
    /// create a new scratch space and return the id
    unsigned int CreateScratchSpace();
    /// remove a scratch space by id
    void RemoveScratchSpace(unsigned int id);
    
    /// ode helper function
    Opcode::Model* GeomTriMeshGetOpcodeModel(const dGeomID& g);

protected:
    /// register an entity with the server
    void RegisterEntity(PhysicsEntity* entity);
    /// unregister an entity from the server
    void UnregisterEntity(PhysicsEntity* entity);

    friend class Level;
    friend class RigidBody;
    friend class TerrainEntity;
    friend class Shape;
    friend class Ray;
    friend class MouseGripper;
    friend class PhysicsEntity;

    static uint UniqueStamp;  
    bool isOpen;
    Timing::Time time;
    Util::Array<ContactPoint> contactPoints;
    Ptr<Level> curLevel;
    Ptr<MeshCache> meshCache;
    Ray ray;

    Util::Dictionary<uint, PhysicsEntity*> entityRegistry;
};

//------------------------------------------------------------------------------
/**
*/
inline
uint
PhysicsServer::GetUniqueStamp()
{
    return ++UniqueStamp;
}

//------------------------------------------------------------------------------
/**
*/
inline
bool
PhysicsServer::IsOpen() const
{
    return isOpen;
}

//------------------------------------------------------------------------------
/**
    Set the current time. Should be called before each call to Trigger().

    @param  t   the current time in seconds
*/
inline
void
PhysicsServer::SetTime(Timing::Time t)
{
    this->time = t;
}

//------------------------------------------------------------------------------
/**
    Get the current time.

    @return     current time
*/
inline
Timing::Time
PhysicsServer::GetTime() const
{
    return this->time;
}

//------------------------------------------------------------------------------
/**
*/
inline
const Util::Array<ContactPoint>&
PhysicsServer::GetContactPoints() const
{
    return this->contactPoints;
}

//------------------------------------------------------------------------------
/**
    Converts the rotational part of a Math::matrix44 to ODE. This includes
    a handedness conversion (Mangalore is right handed, ODE left handed(?)).
*/
inline
void
PhysicsServer::Matrix44ToOde(const Math::matrix44& from, dMatrix3& to)
{
    // write the transpose of the original matrix
    to[0]=from.getrow0().x(); to[1]=from.getrow1().x(); to[2]=from.getrow2().x(); to[3]=0.0f;
    to[4]=from.getrow0().y(); to[5]=from.getrow1().y(); to[6]=from.getrow2().y(); to[7]=0.0f;
    to[8]=from.getrow0().z(); to[9]=from.getrow1().z(); to[10]=from.getrow2().z(); to[11]=0.0f;
}

//------------------------------------------------------------------------------
/**
    Convert a dMatrix3 to the rotational part of a Math::matrix44. Includes
    handedness conversion.
*/
inline
void
PhysicsServer::OdeToMatrix44(const dMatrix3& from, Math::matrix44& to)
{
    to.setrow0(Math::float4(from[0],from[4],from[8],0.0f));
    to.setrow1(Math::float4(from[1],from[5],from[9],0.0f));
    to.setrow2(Math::float4(from[2],from[6],from[10],0.0f));
}

//------------------------------------------------------------------------------
/**
    Convert a Math::vector to an Ode vector.
*/
inline
void
PhysicsServer::Vector3ToOde(const Math::vector& from, dVector3& to)
{
    to[0]=from.x();
    to[1]=from.y(); 
    to[2]=from.z();
    to[3]=1.0f;
}

//------------------------------------------------------------------------------
/**
    Convert an Ode vector to a Math::vector.
*/
inline
void
PhysicsServer::OdeToVector3(const dVector3& from, Math::vector& to)
{
    to.set(from[0], from[1], from[2]);
}

//------------------------------------------------------------------------------
/**
*/
inline
void
PhysicsServer::RegisterEntity(PhysicsEntity* entity)
{
    n_assert(entity)
    this->entityRegistry.Add(entity->GetUniqueId(), entity);
}

//------------------------------------------------------------------------------
/**
*/
inline
void
PhysicsServer::UnregisterEntity(PhysicsEntity* entity)
{
    n_assert(entity);
    this->entityRegistry.Erase(entity->GetUniqueId());
}

//------------------------------------------------------------------------------
/**
    Find a physics entity by its unique id. Can return 0!

    @param  uniqueId    unique id of entity to find
    @return             pointer to entity, or 0 if entity doesn't exist
*/
inline
PhysicsEntity*
PhysicsServer::GetEntityByUniqueId(PhysicsEntity::Id uniqueId) const
{
    PhysicsEntity* entity = 0;
    if (0 != uniqueId && this->entityRegistry.Contains(uniqueId))
    {
        entity = this->entityRegistry[uniqueId];
    }
    return entity;
}

}; // namespace Physics
//------------------------------------------------------------------------------
#endif

